/**
 *    Copyright (C) 2008, 2009  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */

#ifndef		_CUBEFS_MODULES_CONTAINERS_STREAM_POSIX_MANAGER_HPP
#define		_CUBEFS_MODULES_CONTAINERS_STREAM_POSIX_MANAGER_HPP

#include <cubefs/core/container/page_mgr.hpp>
#include <map>
#include <string>

#include <cubefs/api/api.hpp>
#include <cubefs/api/models/logs.hpp>

#include <malloc.h>

#define CUBEFS_SOURCE "modules/containers/stream/all/manager.hpp:" CUBEFS_AT

namespace cubefs {

using namespace cubefs::tools;
using namespace cubefs::ctr;
using namespace cubefs::api;

template <typename Profile, typename PageType, typename TxnType>
class StreamPageManager: public StreamManagerImpl {
public:
	typedef PageType 															Page;
	typedef typename Page::ID 													ID;
	typedef TxnType 															Transaction;
	static const int PAGE_SIZE 													= 4096;

private:
	typedef StreamPageManager<Profile, PageType, TxnType> 						MyType;

	typedef std::map<ID, Page*> 												IDPageMap;

	ID 					root_;
	Int 				counter_;
	Container* 			container_;
	ContainerMetadata* 	metadata_;
	IDPageMap 			pages_;
	bool 				LE_;
	MyType& 			me_;
	std::string 		type_name_;
	BigInt 				allocs_;

	typedef RootMapInterface<typename PageType::ID> MyRootMapInterface;
	MyRootMapInterface* roots_;

public:
	StreamPageManager(Container* container, MyRootMapInterface* roots) :
		counter_(100), container_(container), metadata_(container->GetMetadata()), root_(0), me_(*this),
		type_name_("StreamContainer"), allocs_(0), roots_(roots)
	{}

	virtual ~StreamPageManager() throw () {
		try {
			Int npages = 0;
			for (typename IDPageMap::iterator i = pages_.begin(); i!= pages_.end(); i++) {
				::free(i->second);
				npages++;
			}

			if (allocs_ - npages > 0) {
				CUBEFS_ERROR(me_, "Page leak detected:", npages, allocs_, (allocs_ - npages));
			}
		} catch (...) {
		}
	}

	const ID &root() const {
		return root_;
	}

	virtual Page* for_update(Page *page) {
		return page;
	}

	virtual Page* create_new()
	{
		allocs_++;
		char * buf = (char*) malloc(4096);
		for (int c = 0; c < 4096; c++)
		{
			buf[c] = 0;
		}

		ID id = counter_++;

		Page* p = new (buf) Page(id);

		pages_[id] = p;

		CUBEFS_TRACE(me_, "Page:", p, &p->id(), p->id().value());

		return p;
	}



	virtual void free(const ID &id) {
		Page* page = get(id);
		if (page != NULL)
		{
			pages_.erase(id);
			CUBEFS_DEBUG(me_, "Remove pages with id", id, "size=", pages_.size());
			char* buf = (char*) page;
			::free(buf);
			allocs_--;
		}
		else {
			CUBEFS_ERROR(me_, "There is no page with id", id, "size=", pages_.size());
		}
	}

	virtual void free(Page *page) {
		free(page->id());
	}

	virtual Page *get(const ID &page_id)
	{
		if (page_id.is_null())
		{
			return NULL;
		}
		else {
			typename IDPageMap::iterator i = pages_.find(page_id);
			if (i != pages_.end())
			{
				if (i->second == NULL)
				{
					throw NullPointerException(CUBEFS_SOURCE, "Null page for the specified page_id");
				}
				return i->second;
			}
			else {
				throw NullPointerException(CUBEFS_SOURCE, "Can't find page for the specified page_id");
			}
		}
	}



	virtual void sync(const ID &page_id) {

	}

	Int get_page_size()
	{
		return PAGE_SIZE;
	}

	virtual void load(InputStreamHandler *input)
	{
		char signature[6];

		input->read(signature, sizeof(signature));

		if (!(signature[0] == 'C' && signature[1] == 'U' && signature[2] == 'B' && signature[3] == 'E'))
		{
			throw CubefsException(CUBEFS_SOURCE, "The stream does not start from CUBE signature");
		}

		if (!(signature[4] == 0 || signature[4] == 1))
		{
			throw BoundsException(CUBEFS_SOURCE, "Endiannes filed value is out of bounds", signature[4], 0, 1);
		}

		if (signature[5] != 0)
		{
			throw CubefsException(CUBEFS_SOURCE, "This is not a stream container");
		}

		bool first = true;
		Short size;
		while (input->read(size))
		{
			char buf[PAGE_SIZE];
			for (Int c = 0; c < PAGE_SIZE; c++)
				buf[c] = 0;

			input->read(buf, 0, size);

			Page* page = (Page*) buf;
			PageMetadata* pageMetadata = metadata_->GetPageMetadata(page->page_type_hash());

			CUBEFS_TRACE(me_, "Read page with hashes", page->page_type_hash(), page->model_hash(), "of size", size, "id", page->id(), "metadata", pageMetadata);

			char* mem = new char[PAGE_SIZE];
			for (Int c = 0; c < PAGE_SIZE; c++)
			{
				mem[c] = 0;
			}

			FieldMetadata* last_field = pageMetadata->GetField(size, true);
			Int limit = last_field != NULL ? last_field->Ptr() : PAGE_SIZE;

			pageMetadata->Internalize(page, mem, limit);

			pages_[page->id()] = (Page*) mem;

			if (first)
			{
				root_ = page->id();
				first = false;
			}
		}

		Int maxId = -1;
		for (typename IDPageMap::iterator i = pages_.begin(); i != pages_.end(); i++)
		{
			Int idValue = i->second->id().value();
			if (idValue > maxId)
			{
				maxId = idValue;
			}
		}

		roots_->set_root(root_);

		counter_ = maxId + 1;
	}

	virtual void store(OutputStreamHandler *output)
	{
		char signature[6] = "CUBE";

		signature[4] = 0;
		signature[5] = 0;

		output->write(&signature, 0, sizeof(signature));

		char buf[PAGE_SIZE];

		if (!root_.is_null())
		{
			dump_page(output, buf, pages_[root_]);

			for (typename IDPageMap::iterator i = pages_.begin(); i!= pages_.end(); i++)
			{
				Page *page = i->second;
				if (page != NULL && !(page->id() == root_))
				{
					dump_page(output, buf, page);
				}
			}
		}

		output->close();
	}

	void dump_page(OutputStreamHandler *output, char* buf, Page *page) {
		if (page->page_type_hash() != 0)
		{
			CUBEFS_TRACE(me_, "Dump page with hashes", page->page_type_hash(), page->model_hash(), "with id", page->id());
			PageMetadata* pageMetadata = metadata_->GetPageMetadata(page->page_type_hash());

			for (Int c = 0; c < PAGE_SIZE; c++)
			{
				buf[c] = 0;
			}

			pageMetadata->Externalize(page, buf);

			Int ptr = pageMetadata->GetDataBlockSize(page);
			FieldMetadata* last_field = pageMetadata->GetField(ptr, false);

			Short size = last_field != NULL ? last_field->AbiPtr() : PAGE_SIZE;

			output->write(size);
			output->write(buf, 0, size);
		}
		else {
			CUBEFS_TRACE(me_, "hash for page", page->id(), "is not specified");
		}
	}

	void set_root(BigInt name, const ID &page_id) {
		if (name == 0)
		{
			root_ = page_id;
		}
		else {
			roots_->set_value_for_key(name, page_id);
		}
	}

	void remove_root(BigInt name) {
		if (name == 0)
		{
			root_.Clear();
		}
		else {
			roots_->remove_by_key(name);
		}
	}

	virtual void new_root(BigInt name, const ID &page_id) {
		CUBEFS_TRACE(me_, "Register new root", page_id, "for", name);

		if (page_id.is_null())
		{
			remove_root(name);
		}
		else {
			set_root(name, page_id);
		}
	}

	bool is_log(Int level) {
		return level >= logger().GetLogLevel();
	}

	cubefs::api::Logger& logger() {
		return Base::logger();
	}

	StringRef type_name() {
		return type_name_;
	}

	cubefs::api::Manager* get_manager() {
		return this;
	}

	void set_manager(cubefs::api::Manager*)
	{
		throw CubefsException(CUBEFS_SOURCE, "This Stream container doesn't allows manual page manager setup");
	}




	// Manager implementaion

	virtual Container* GetContainer() {
		return container_;
	}

	virtual void SetContainer(Container* container) {
		throw CubefsException(CUBEFS_SOURCE, "This Stream container doesn't allows manual page manager setup");
	}

	virtual cubefs::api::Page* CreatePageWrapper()
	{
		return new PageWrapper<Page, PAGE_SIZE>();
	}

	virtual void GetRootPageId(TypedIDValue* id)
	{
		if (id == NULL)
			throw NullPointerException(CUBEFS_SOURCE, "id must not be null");

		IDValue idValue(&root_);
		id->SetValue(idValue);
	}

	virtual void SetRootPageId(TypedIDValue* id) {
		if (id == NULL)
			throw NullPointerException(CUBEFS_SOURCE, "id must not be null");

		root_ = id->GetValue();
	}

	virtual BigInt GetPageCount() {
		return pages_.size();
	}

	virtual void GetPage(cubefs::api::Page* page, TypedIDValue* idValue)
	{
		if (idValue == NULL)
			throw NullPointerException(CUBEFS_SOURCE, "id must not be null");

		if (page == NULL)
			throw NullPointerException(CUBEFS_SOURCE, "page must not be null");

		page->SetPtr(this->get(idValue->GetValue()));
	}


	virtual void RemovePage(TypedIDValue* idValue)
	{
		if (idValue == NULL)
			throw NullPointerException(CUBEFS_SOURCE, "id must not be null");

		this->free(ID(idValue->GetValue()));
	}

	virtual void CreateNewPage(int flags, cubefs::api::Page* page)
	{
		if (page == NULL)
			throw NullPointerException(CUBEFS_SOURCE, "page must not be null");

		if ((flags && Manager::ROOT) == 0)
		{
			page->SetPtr(this->create_new());
		}
		else if (root_.is_null())
		{
			Page* page0 = this->create_new();
			page->SetPtr(page0);
			root_ = page0->id();
		}
		else {
			throw CubefsException(CUBEFS_SOURCE, "Root page has been already created");
		}
	}

	void* internal_page_mgr() {
		throw CubefsException(CUBEFS_SOURCE, "This method must not be called anywhere");
	}
};

}

#undef CUBEFS_SOURCE

#endif
